package indi.zhifa.recipe.bailan.framework.auth.util.impl;

import com.alibaba.fastjson2.JSON;
import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTVerificationException;
import com.auth0.jwt.exceptions.SignatureVerificationException;
import com.auth0.jwt.exceptions.TokenExpiredException;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.auth0.jwt.interfaces.JWTVerifier;
import indi.zhifa.recipe.bailan.framework.auth.entity.BaseTokenObject;
import indi.zhifa.recipe.bailan.framework.auth.entity.exception.AuthException;
import indi.zhifa.recipe.bailan.framework.auth.property.TokenConfig;
import indi.zhifa.recipe.bailan.framework.auth.util.IBaseTokenUtil;
import indi.zhifa.recipe.bailan.framework.common.entity.exception.ServiceException;
import indi.zhifa.recipe.bailan.framework.common.util.LocalDateTimeUtil;
import indi.zhifa.recipe.bailan.framework.web.common.util.ZFHttpUtil;

import javax.servlet.http.HttpServletRequest;
import java.time.LocalDateTime;
import java.util.Date;


public abstract class BaseTokenUtilImpl implements IBaseTokenUtil {

    protected final TokenConfig mTokenConfig;
    protected final Algorithm algorithm;
    protected final JWTVerifier verifier;

    public BaseTokenUtilImpl(TokenConfig pTokenConfig){
        mTokenConfig = pTokenConfig;
        algorithm = Algorithm.HMAC256(mTokenConfig.getJwtConfig().getSecret());
        verifier = JWT.require(algorithm).build();
    }


    public String generateToken(BaseTokenObject pTokenObject){
        LocalDateTime expireTime = LocalDateTime.now().plusSeconds(
                mTokenConfig.getJwtConfig().getDuration() * 60);
        Date dateExpireTime = LocalDateTimeUtil.localDateTimeToDate(expireTime);

        String token = JWT.create()
                .withSubject(JSON.toJSONString(pTokenObject))
                .withExpiresAt(dateExpireTime)
                .sign(algorithm);
        String prefix = mTokenConfig.getJwtConfig().getPrefix();
        return prefix+token;
    }

    @Override
    public BaseTokenObject putTokenIntoAttribute(BaseTokenObject pTokenObject) {
        HttpServletRequest request = ZFHttpUtil.getServletRequest();
        String requestKey = mTokenConfig.getRequestKey();
        request.setAttribute(requestKey,pTokenObject);
        return pTokenObject;
    }

    public boolean hasTokenObject(){
        HttpServletRequest request;
        try{
            request = ZFHttpUtil.getServletRequest();
        }catch (Exception ex){
            request = null;
        }
        if(null == request){
            return false;
        }
        String requestKey = mTokenConfig.getRequestKey();
        Object objTokenObject = request.getAttribute(requestKey);
        return null != objTokenObject;
    }

    /**
     * 获取token对象
     *
     * @return
     */
    public abstract BaseTokenObject getTokenObject();

    public <T extends BaseTokenObject> T getTokenObject(Class<T> pTokenClass) throws ServiceException {
        HttpServletRequest request = ZFHttpUtil.getServletRequest();
        String requestKey = mTokenConfig.getRequestKey();
        Object objTokenObject = request.getAttribute(requestKey);
        if(null == objTokenObject){
            throw new AuthException("还没有设置token");
        }
        if(!pTokenClass.isInstance(objTokenObject)){
            throw new ServiceException("tokenObject 无法转换为 "+pTokenClass.getName());
        }
        return (T) objTokenObject;
    }

    /**
     * 解析token
     *
     * @param pToken token字符串
     * @return
     */
    public abstract BaseTokenObject parseToken(String pToken);

    protected <T extends BaseTokenObject> T parseToken(String pToken, Class<T> pTokenClass){
        try{
            DecodedJWT decodedJWT;
            try{
                decodedJWT = JWT.decode(pToken);
                decodedJWT = verifier.verify(decodedJWT);
            }catch (TokenExpiredException tokenExpiredException){
                throw new AuthException("签名超期"+tokenExpiredException.getMessage());
            }catch (SignatureVerificationException signatureVerificationException){
                throw new AuthException("签名被篡改"+signatureVerificationException.getMessage());
            }catch (JWTVerificationException jwtVerificationException){
                throw new AuthException("签名不合法"+jwtVerificationException.getMessage());
            }
            Date expireTimeDate = decodedJWT.getExpiresAt();
            LocalDateTime expireTime = LocalDateTimeUtil.dateToLocalDateTime(expireTimeDate);
            if(LocalDateTime.now().isAfter(expireTime)){
                throw new AuthException("token过期");
            }
            String subject = decodedJWT.getSubject();
            T tokenObject = JSON.parseObject(subject,pTokenClass);
            return tokenObject;
        }
        catch (ServiceException ex){
            throw new AuthException("解析token时发生错误，错误信息是: "+ex.getMsg());
        }
        catch (Exception ex){
            throw new AuthException("解析token时发生错误，错误信息是: "+ex);
        }
    }

}
